package me.yricky.abcde.cli

import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import me.yricky.abcde.AppState
import me.yricky.abcde.page.CodeView
import me.yricky.abcde.util.SelectedAbcFile
import me.yricky.abcde.util.SelectedIndexFile
import me.yricky.oh.abcd.cfm.AbcClass
import me.yricky.oh.abcd.isa.asmArgs
import me.yricky.oh.abcd.isa.util.ExternModuleParser
import me.yricky.oh.abcd.isa.util.V2AInstParser
import me.yricky.oh.resde.ResType
import java.io.*
import java.nio.file.Files
import java.nio.file.Paths
import java.util.zip.ZipFile

class CliFunc(
    val name: String,
    val usage: String,
    val action: (List<String>) -> Unit
)

val dumpClass = Pair(
    "--dump-class",
    CliFunc(
        "Dump class",
        "/path/to/module.abc --out=/path/to/outFile.txt"
    ) { args ->
        val iterator = args.iterator()
        var outFile: OutputStream = System.out
        var inputFile: SelectedAbcFile? = null
        while (iterator.hasNext()) {
            val arg = iterator.next()
            if (arg.startsWith("--out=")) {
                outFile = File(arg.removePrefix("--out=")).outputStream()
            } else {
                inputFile = SelectedAbcFile(File(arg))
            }
        }
        println("${inputFile?.file?.path}")
        if (inputFile?.valid() == true) {
            val ps = PrintStream(outFile)
            val appState: AppState = AppState().apply {
                open(inputFile)
            }
            val operandParser = listOf(V2AInstParser, ExternModuleParser)
            val session = appState.currHapSession
            inputFile.abcBuf.classes.forEach { (k, v) ->
                if (v is AbcClass) {
                    v.methods.forEach { method ->
                        method.codeItem?.asm?.list?.forEach { it ->
                            it.asmArgs(CodeView.operandParser).forEach { (index, argString) ->
                                if (argString != null && argString.contains("str:\"id\", I32:")) {
                                    println("class: " + v.name)
                                    println("methos: " + method.name)
                                    val start = "str:\"id\", I32:"
                                    val end = ","
                                    val startIndex = argString.indexOf(start)
                                    val endIndex = argString.indexOf(end, startIndex + start.length)
                                    val resId = if (endIndex == -1) {
                                        argString.substring(startIndex + start.length until argString.length)
                                    } else {
                                        argString.substring(startIndex + start.length until endIndex)
                                    }
                                    println("arg: $argString")
                                    println("id: $resId")
                                }
                            }
                        }
                    }
                }
                ps.println(v.name)
            }
        }
    }
)

val dumpIndex = Pair(
    "--dump-index",
    CliFunc(
        "Dump index",
        "/path/to/resource.index --out=/path/to/outFile.json"
    ) { args ->
        val iterator = args.iterator()
        var outFile: OutputStream = System.out
        var inputFile: SelectedIndexFile? = null
        while (iterator.hasNext()) {
            val arg = iterator.next()
            if (arg.startsWith("--out=")) {
                outFile = File(arg.removePrefix("--out=")).outputStream()
            } else {
                inputFile = SelectedIndexFile(File(arg))
            }
        }
        val json = Json {
            prettyPrint = true
        }
        println("${inputFile?.file?.path}")
        val map = mutableMapOf<Int, List<Map<String, String>>>()
        if (inputFile?.valid() == true) {
            val ps = PrintStream(outFile)
            inputFile.resBuf.resMap.forEach { (t, u) ->
                map[t] = u.map {
                    mapOf(
                        "type" to it.resType.toString(),
                        "param" to it.limitKey,
                        "name" to it.fileName,
                        "data" to it.data
                    )
                }
            }
            ps.print(json.encodeToString(map))
        }
    }
)

val resourceReference = Pair(
    "--res-reference",
    CliFunc(
        "Dump resource reference",
        "/path/to/module.abc --index=/path/to/resources.index --hap=/path/to/hap.hap --out=/path/to/outFile.txt"
    ) { args ->
        val iterator = args.iterator()
        var outFile: OutputStream = System.out
        var inputFile: SelectedAbcFile? = null
        var indexFile: SelectedIndexFile? = null
        var hapFile: ZipFile? = null
        val rawMap = mutableMapOf<String, Int>()
        var temp = 0
        var hapSize = 0.0;
        while (iterator.hasNext()) {
            val arg = iterator.next()
            if (arg.startsWith("--out=")) {
                outFile = File(arg.removePrefix("--out=")).outputStream()
            } else if (arg.startsWith("--hap=")) {
                hapFile = ZipFile(File(arg.removePrefix("--hap=")))
                hapSize = Files.size(Paths.get(arg.removePrefix("--hap="))) / 1024.0 / 1024.0
            }
        }
        val json = Json {
            prettyPrint = true
        }
        if (hapFile != null) {
            var abcEntry = hapFile.getEntry("ets/modules.abc")
            var abcOutPut = FileOutputStream("modules.abc")
            var bufferedOutputStream = BufferedOutputStream(abcOutPut)
            if (abcEntry != null) {
                var abcInput = hapFile.getInputStream(abcEntry)
                var bufferedInputStram = BufferedInputStream(abcInput)
                temp = bufferedInputStram.read()
                while (temp != -1) {
                    bufferedOutputStream.write(temp)
                    temp = bufferedInputStram.read()
                }
                bufferedOutputStream.flush()
                abcInput.close()
                abcOutPut.close()
                inputFile = SelectedAbcFile(File("modules.abc"))
            }
            var indexEntry = hapFile.getEntry("resources.index")
            var indexOutPut = FileOutputStream("resources.index")
            bufferedOutputStream = BufferedOutputStream(indexOutPut)
            if (indexEntry != null) {
                var indexInput = hapFile.getInputStream(indexEntry)
                var bufferedInputStram = BufferedInputStream(indexInput)
                temp = bufferedInputStram.read()
                while (temp != -1) {
                    bufferedOutputStream.write(temp)
                    temp = bufferedInputStram.read()
                }
                bufferedOutputStream.flush()
                indexInput.close()
                indexOutPut.close()
                indexFile = SelectedIndexFile(File("resources.index"))
            }
        }
        var entries = hapFile?.entries()
        while (entries != null && entries.hasMoreElements()) {
            val entryName = entries.nextElement().name
            if (entryName.contains("/rawfile/")) {
                rawMap[entryName.substring(entryName.indexOf("/rawfile/") + 9)] = 0
            }
        }
        if (inputFile?.valid() == true && indexFile?.valid() == true) {
            val ps = PrintStream(outFile)
            val map = mutableMapOf<String, Int>()
            val outputMap = mutableMapOf<String, Int>()
            inputFile.abcBuf.classes.forEach { (k, v) ->
                if (v is AbcClass) {
                    v.methods.forEach { method ->
                        method.codeItem?.asm?.list?.forEach { it ->
                            it.asmArgs(CodeView.operandParser).forEach { (index, argString) ->
                                if (argString != null && argString.contains("str:\"id\", I32:")) {
                                    val start = "str:\"id\", I32:"
                                    val end = ","
                                    val startIndex = argString.indexOf(start)
                                    val endIndex = argString.indexOf(end, startIndex + start.length)
                                    val resId = if (endIndex == -1) {
                                        argString.substring(startIndex + start.length until argString.length)
                                    } else {
                                        argString.substring(startIndex + start.length until endIndex)
                                    }
                                    map[resId] = if (map.contains(resId)) map[resId]!! + 1 else 1
                                } else if (argString != null) {
                                    rawMap.keys.forEach { key ->
                                        run {
                                            if (argString.contains(key) && !argString.contains('/' + key)) {
                                                rawMap[key] = rawMap[key]!! + 1
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
            indexFile.resBuf.resMap.forEach { (id, res) ->
                if (res.get(0).resType == ResType.RAW || res.get(0).resType == ResType.MEDIA) {
                    outputMap[res.get(0).data] = map.getOrDefault(id.toString(), 0)
                }
            }
            rawMap.forEach { (k, v) ->
                outputMap["entry/resources/rawfile/" + k] = v
            }
            val sortedId = outputMap.keys.sortedBy { outputMap[it] }
            var totalSize = 0.0;
            var totalResSize = 0.0;
            sortedId.forEach {
                val tmpIt = it.substring(it.indexOf("/") + 1)
                if (outputMap.get(it) == 0 && hapFile != null) {
                    totalSize += hapFile!!.getEntry(tmpIt).compressedSize / 1024.0 / 1024.0
                }
                totalResSize += hapFile!!.getEntry(tmpIt).compressedSize / 1024.0 / 1024.0
                ps.println(it + " : " + outputMap.get(it))
            }
            ps.println(
                String.format(
                    "total compressed size is %.2f MB, occpuying %.2f%% of the hap file, occpuing %.2f%% of all resource files",
                    totalSize,
                    totalSize * 100 / hapSize,
                    totalSize * 100 / totalResSize
                )
            )
        }
    }
)
